package backend.service.charisma;

import backend.model.admin.Employee;
import backend.model.worklog.WorkLog;
import backend.model.worklog.WorkLogMismatch;
import backend.model.charisma.CharismaWorkLog;
import backend.service.admin.EmployeeService;
import backend.service.worklog.WorkLogMismatchService;
import backend.service.worklog.WorkLogService;
import shared.util.CalendarUtils;
import org.apache.commons.lang.time.DateUtils;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import shared.exceptions.CharismaConnectionDownException;

import java.io.Serializable;
import java.util.*;
import java.util.concurrent.Future;

@Service
public class CharismaDataSynchronizer implements Serializable {
    private static Logger logger = Logger.getLogger(CharismaDataSynchronizer.class);

    @Autowired
    private CharismaWorkLogService charismaWorkLogService;
    @Autowired
    private WorkLogService workLogService;
    @Autowired
    private WorkLogMismatchService workLogMismatchService;
    @Autowired
    private EmployeeService employeeService;

    private Future charismaHoursSynchTask;

    public void setCharismaHoursSynchTask(Future future) {
        this.charismaHoursSynchTask = future;
    }

    public Future getCharismaHoursSynchTask() {
        return charismaHoursSynchTask;
    }

    public void validateYesterdayHoursForAllEmployees() throws CharismaConnectionDownException {
        List<Employee> employees = employeeService.findAllActiveEmployees();
        Calendar cal = Calendar.getInstance();
        cal.add(Calendar.DATE, -1);
        Date yesterday = DateUtils.truncate(cal.getTime(), Calendar.DATE);
        validateHoursForEmployeeListAndTimeInterval(employees, yesterday, yesterday );
    }

    public void validateHoursForEmployeeAndDate(Employee employee, Date date) throws CharismaConnectionDownException {
        validateHoursForEmployeeListAndTimeInterval(Arrays.asList(employee), date, date);
    }

    public void validateHoursForEmployeeAndTimeInterval(Employee employee, Date startDate, Date endDate) throws CharismaConnectionDownException {
        validateHoursForEmployeeListAndTimeInterval(Arrays.asList(employee) ,startDate, endDate);
    }

    public void validateHoursForAllEmployeesAndTimeInterval(Date startDate, Date endDate) throws CharismaConnectionDownException {
        List<Employee> employees = employeeService.findAllActiveEmployees();
        validateHoursForEmployeeListAndTimeInterval(employees, startDate, endDate);
    }

    public void validateHoursForEmployeeListAndTimeInterval(List<Employee> employeesList, Date startDate, Date endDate) throws CharismaConnectionDownException {
        for (Employee employee : employeesList) {
            logger.debug("----------------------------------------------------------------------------------------------------------");
            logger.debug("Charisma synchronization started for employee " + employee.getDisplayLabel() +
                    " and time interval " + CalendarUtils.formatDate(startDate) + " -> " + CalendarUtils.formatDate(endDate));
            clearWorkLogMismatchesForEmployeeAndTimeInterval(startDate, endDate, employee);

            Map<Date, Integer> localDateToHoursMap = getLocalDateToHoursMap(startDate, endDate, employee);
            Map<Date, Integer> charismaDateToHoursMap = getCharismaDateToHoursMap(startDate, endDate, employee);

            Set<Date> unprocessedCharismaHours = charismaDateToHoursMap.keySet();
            for (Date date : localDateToHoursMap.keySet()) {
                Integer localHours = localDateToHoursMap.get(date);
                Integer charismaHours = charismaDateToHoursMap.get(date);
                localHours = (localHours == null) ? 0 : localHours;
                charismaHours  = (charismaHours == null) ? 0 : charismaHours;
                if (localHours != charismaHours) {
                    logger.debug("Found hours mismatch for employee " + employee.getDisplayLabel() + " on date " + CalendarUtils.formatDate(date)+
                            ": Charisma hours is " + charismaHours + " - local hours is " + localHours);
                    insertNewWorkLogMismatch(date, employee, localHours, charismaHours);
                }
                unprocessedCharismaHours.remove(date);
            }

            for (Date date : unprocessedCharismaHours) {
                Integer charismaHours = charismaDateToHoursMap.get(date);
                if (charismaHours > 0) {
                    logger.debug("Found hours mismatch for employee " + employee.getDisplayLabel()  + " on date " + CalendarUtils.formatDate(date) +
                            ": Charisma hours is " + charismaHours + " - local hours is 0");
                    insertNewWorkLogMismatch(date, employee, 0, charismaHours);
                }
            }
            logger.debug("Charisma synchronization ended for employee " + employee.getDisplayLabel());
        }
    }

    public int getCharismaHoursForDateAndEmployee(Date date, Employee employee) throws CharismaConnectionDownException {
        CharismaWorkLog charismaWorkLog = charismaWorkLogService.loadCharismaWorkLogForDateAndEmployee(date, employee);
        return charismaWorkLog != null ? charismaWorkLog.getHours() : 0;
    }

    private void clearWorkLogMismatchesForEmployeeAndTimeInterval(Date startDate, Date endDate, Employee employee) {
        logger.debug("Clearing all mismatches for employee " + employee.getDisplayLabel() +
                " and time interval " + CalendarUtils.formatDate(startDate) + " -> " + CalendarUtils.formatDate(endDate));
        workLogMismatchService.deleteWorkLogMismatchesForTimeIntervalAndEmployee(employee, startDate, endDate);
    }

    private Map<Date, Integer> getLocalDateToHoursMap(Date startDate, Date endDate, Employee employee) {
        HashMap<Date, Integer> dateToHoursMap = new HashMap<Date, Integer>();
        List<WorkLog> localWorkLogs = workLogService.loadWorkLogForTimeIntervalAndEmployee(employee, startDate, endDate);
        for (WorkLog workLog : localWorkLogs) {
            Date date = DateUtils.truncate(workLog.getDate(), Calendar.DATE);
            Integer hours = dateToHoursMap.get(date);
            if (hours != null) {
                hours += workLog.getHours();
                dateToHoursMap.put(date, hours);
            } else {
                dateToHoursMap.put(date, workLog.getHours());
            }
        }
        return dateToHoursMap;
    }

    private Map<Date, Integer> getCharismaDateToHoursMap(Date startDate, Date endDate, Employee employee) throws CharismaConnectionDownException {
        HashMap<Date, Integer> dateToHoursMap = new HashMap<Date, Integer>();
        List<CharismaWorkLog> localWorkLogs = charismaWorkLogService.loadCharismaWorkLogForTimeIntervalAndEmployee(startDate, endDate, employee);
        for (CharismaWorkLog workLog : localWorkLogs) {
            Date date = new Date(workLog.getCompoundKey().getDate().getTime());
            Integer hours = dateToHoursMap.get(date);
            if (hours != null) {
                hours += workLog.getHours();
            } else {
                dateToHoursMap.put(date, workLog.getHours());
            }
        }
        return dateToHoursMap;
    }

    private void insertNewWorkLogMismatch(Date date, Employee employee, int localHours, int charismaHours) {
        WorkLogMismatch workLogMismatch = newWorkLogMismatchEntry(date, employee, localHours, charismaHours);
        workLogMismatchService.saveDBModel(workLogMismatch);
    }

    private WorkLogMismatch newWorkLogMismatchEntry(Date date, Employee employee, int localHours, int charismaHours) {
        WorkLogMismatch workLogMismatch = new WorkLogMismatch();
        workLogMismatch.setDate(date);
        workLogMismatch.setEmployee(employee);
        workLogMismatch.setLocalHours(localHours);
        workLogMismatch.setCharismaHours(charismaHours);
        return workLogMismatch;
    }
}
